using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;

namespace PrisonStep
{
    /// <summary>
    /// This class implements one section of our prison ship
    /// </summary>
    public class PrisonModel
    {
        #region Fields

        enum DoorStates { Closed, Opening, Open, Closing };

        struct Doors
        {
            public int mDoorNumber;

            public int mBoneNumber;

            public float mDoorHeight;

            public DoorStates mDoorStates;

            public float DoorHeight { get { return mDoorHeight; } set { mDoorHeight = value; } }

        };

        /// <summary>
        /// The section (6) of the ship
        /// </summary>
        private int section;

        /// <summary>
        /// The name of the asset (FBX file) for this section
        /// </summary>
        private string asset;

        /// <summary>
        /// The game we are associated with
        /// </summary>
        private PrisonGame game;

        /// <summary>
        /// The XNA model for this part of the ship
        /// </summary>
        private Model model;

        /// <summary>
        /// To make animation possible and easy, we save off the initial (bind) 
        /// transformation for all of the model bones. 
        /// </summary>
        private Matrix[] bindTransforms;

        /// <summary>
        /// The is the transformations for all model bones, potentially after we
        /// have made some change in the tranformation.
        /// </summary>
        private Matrix[] boneTransforms;

        /// <summary>
        /// A list of all of the door bones in the model.
        /// </summary>
        private List<int> doors = new List<int>();

        /// <summary>
        /// The xz coordinates of the door locations in the model
        /// </summary>
        private float[] mDoorLocations = {218, 1023, -11, -769, 587,
                                          -999, 787, -763, 1187, -1218};
        private List<Doors> mDoors = new List<Doors>();
        /// <summary>
        /// A list of what doors are currently opening
        /// </summary>
        //private List<DoorStates> mDoorStates = new List<DoorStates>();

        private const float mDoorOpenTime = 2.0f;

        private const float mSlimeTime = 1.0f;

        private bool mSlimed = false;

        private float mWeight = 1.0f;

        #endregion

        #region Properties

        public bool Slimed { set { mSlimed = value; } }

        public float SlimeWeight { get { return mWeight; } }
        #endregion

        #region Construction and Loading

        /// <summary>
        /// Constructor. Creates an object for a section.
        /// </summary>
        /// <param name="game"></param>
        /// <param name="section"></param>
        public PrisonModel(PrisonGame game, int section)
        {
            this.game = game;
            this.section = section;
            this.asset = "AntonPhibes" + section.ToString();
        }

        /// <summary>
        /// This function is called to load content into this component
        /// of our game.
        /// </summary>
        /// <param name="content">The content manager to load from.</param>
        public void LoadContent(ContentManager content)
        {
            // Load the second model
            model = content.Load<Model>(asset);

            // Save off all of hte bone information
            int boneCnt = model.Bones.Count;
            bindTransforms = new Matrix[boneCnt];
            boneTransforms = new Matrix[boneCnt];

            model.CopyBoneTransformsTo(bindTransforms);
            model.CopyBoneTransformsTo(boneTransforms);

            // Find all of the doors and save the index for the bone
            for (int b = 0; b < boneCnt; b++)
            {
                string lModelName = model.Bones[b].Name;
                if (lModelName.StartsWith("DoorInner") || lModelName.StartsWith("DoorOuter"))
                {
                    Doors lTemp = new Doors();
                    lTemp.mBoneNumber = b;

                    char lInt = lModelName[lModelName.Length - 1];
                    lTemp.mDoorNumber = (int)Char.GetNumericValue(lInt);
                    lTemp.mDoorStates = DoorStates.Closed;
                    lTemp.mDoorHeight = 0.0f;
                    mDoors.Add(lTemp);
                }

            }            
        }

        #endregion

        #region Update and Draw

        /// <summary>
        /// This function is called to update this component of our game
        /// to the current game time.
        /// </summary>
        /// <param name="gameTime"></param>
        public void Update(GameTime gameTime)
        {
            float delta = (float)gameTime.ElapsedGameTime.TotalSeconds;
            // As supplied, all of the doors are closed. This code opens each door by 
            // translating it up 2 meters.
            for ( int i = 0; i < mDoors.Count; i++)
            {
                Doors d = mDoors[i];
                switch (d.mDoorStates)
                {
                    case DoorStates.Opening:
                        {
                            if ( d.DoorHeight <= 200 )
                                d.DoorHeight += (float)(200 * delta / mDoorOpenTime);
                            break;
                        }
                    /*//case DoorStates.Closing:
                    //    {
                            if (d.DoorHeight >= 0)
                                d.DoorHeight -= (float)(200 * delta / mDoorOpenTime);
                            break;
                    //    }*/
                }
                if (d.DoorHeight >= 0 && !(d.mDoorStates == DoorStates.Opening))
                    d.DoorHeight -= (float)(200 * delta / mDoorOpenTime);
                boneTransforms[d.mBoneNumber] = Matrix.CreateTranslation(0, d.mDoorHeight, 0) * bindTransforms[d.mBoneNumber];
                mDoors[i] = d;
            }

            if (mSlimed)
            {
                mWeight -= (float)(0.6 * delta / mSlimeTime);
                if (mWeight < 0.4f)
                    mWeight = 0.4f;
            } 
            else
            {
                mWeight += (float)(0.6 * delta / mSlimeTime);
                if (mWeight > 1.0f)
                    mWeight = 1.0f;
            }   
        }

        /// <summary>
        /// This function is called to draw this game component.
        /// </summary>
        /// <param name="graphics"></param>
        /// <param name="gameTime"></param>
        public void Draw(GraphicsDeviceManager graphics, GameTime gameTime)
        {
            
            DrawModel(graphics, model, Matrix.Identity);
        }

        private void DrawModel(GraphicsDeviceManager graphics, Model model, Matrix world)
        {
            // Apply the bone transforms
            Matrix[] absoTransforms = new Matrix[model.Bones.Count];
            model.CopyBoneTransformsFrom(boneTransforms);
            model.CopyAbsoluteBoneTransformsTo(absoTransforms);

            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (Effect effect in mesh.Effects)
                {
                    effect.Parameters["World"].SetValue(absoTransforms[mesh.ParentBone.Index] * world);
                    effect.Parameters["View"].SetValue(game.Camera.View);
                    effect.Parameters["Projection"].SetValue(game.Camera.Projection);
                    effect.Parameters["Weight"].SetValue(mWeight);
                }
                mesh.Draw();
            }
        }

        #endregion

        public bool CheckForOpenDoors(int aDoorNumber, Vector3 aOrienationVector, Vector3 aCameraEye)
        {
            bool lOpen = false;

            for ( int i = 0; i < mDoors.Count; i++)
            {
                // Our current door
                Doors lCurrDoor = mDoors[i];
                // We've found the door
                if (mDoors[i].mDoorNumber == aDoorNumber)
                {
                    Vector3 lVect2 = new Vector3(mDoorLocations[2 * (aDoorNumber - 1)], aCameraEye.Y, mDoorLocations[2 * (aDoorNumber - 1) + 1]) - aCameraEye;
                    float dotprod = Vector3.Dot(aOrienationVector, lVect2);
                    if ( dotprod > 0 )
                        lCurrDoor.mDoorStates = DoorStates.Opening;
                    else
                        lCurrDoor.mDoorStates = DoorStates.Closing;
                    mDoors[i] = lCurrDoor;
                    if (lCurrDoor.DoorHeight <= 200)
                    {
                    
                        if (dotprod > 0)
                            lOpen = false;
                        else
                            lOpen = true;
                    }
                    else
                        lOpen = true;
                }
            }


            return lOpen;

        }

        /// <summary>
        /// Closes the open doors
        /// </summary>
        public void CloseOpenDoors()
        {
            for (int i = 0; i < mDoors.Count; i++)
            {
                Doors lDoor = mDoors[i];
                if (lDoor.mDoorStates == DoorStates.Opening)
                    lDoor.mDoorStates = DoorStates.Closing;

                mDoors[i] = lDoor;
            }
        }
    }
}
